package com.foreveross.system.config;


import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.commons.lang3.StringUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.regex.Pattern;

/**
 * Created by shaowin on 2016/8/4.
 * <p/>
 * traefik 负载均衡 自注册
 */
@Component
@Profile("prod")
public class TraefikZkRegister {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private ZkClient zkClient;

    private final String LOCALHOST = "127.0.0.1";

    private final String ANYHOST = "0.0.0.0";

    private final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");

    @Value("${zookeeper.address:127.0.0.1:2181}")
    private String zkAddress;

    @Value("${server.context-path:/}")
    private String contextPath;

    @Value("${server.port:80}")
    private Integer port;

    @Value("${server.weight:1}")
    private Integer weightValue;


    public TraefikZkRegister() {}


    public void init() {
        this.zkClient = new ZkClient(this.zkAddress);
    }

    /**
     * 启动注册到zookeeper触发traefik
     */
    public void startRegister() {
        //去除zkClient的序列化，因为默认是序列化了，但是traefik去读取节点的值的时候是用zookeeper没有进行反序列, 会导致数据乱码
        zkClient.setZkSerializer(new ZkSerializer() {
            @Override
            public byte[] serialize(Object o) throws ZkMarshallingError {
                return (byte[]) o;
            }

            @Override
            public Object deserialize(byte[] bytes) throws ZkMarshallingError {
                return bytes;
            }
        });

        //获取本机地址
        String hostAddress = getLocalAddress0().getHostAddress();


        //如：/appbricks/backends/security/servers/10-155-23-11:8088
        String serviceLongPath = "/appbricks/backends" + contextPath + "/servers/" + hostAddress + ":" + port;
        String urlLongPath = serviceLongPath + "/url";
        String weightLongPath = serviceLongPath + "/weight";

        logger.debug("servers path is:" + serviceLongPath);
        logger.debug("url is:" + urlLongPath);
        logger.debug("weight path is:" + weightLongPath);
        try {
            boolean serverFlag = false;
            boolean urlFlag = false;
            boolean weightFlag = false;
            //创建server1
            if (!exists(serviceLongPath)) {
                serverFlag = this.createZnode(serviceLongPath, null, CreateMode.PERSISTENT);
            } else {//已经存在则更新
                serverFlag = this.updateZnode(serviceLongPath, null);
            }
            //创建server1/url
            if (serverFlag) {
                logger.info("create/update znode:" + serviceLongPath + " succeed!");
                if (!exists(urlLongPath)) {
                    urlFlag = createZnode(urlLongPath, "http://" + hostAddress + ":" + port, CreateMode.PERSISTENT);
                } else {//存在则更新
                    urlFlag = updateZnode(urlLongPath, "http://" + hostAddress + ":" + port);
                }
            } else {
                logger.error("create/update znode:" + serviceLongPath + " fail!");
            }

            //创建server1/weight
            if (urlFlag) {
                logger.info("create/update znode:" + urlLongPath + " succeed!");
                if (!exists(weightLongPath)) {
                    weightFlag = createZnode(weightLongPath, weightValue.toString(), CreateMode.PERSISTENT);
                } else {
                    weightFlag = updateZnode(weightLongPath, weightValue.toString());
                }
            } else {
                logger.error("create znode:" + urlLongPath + " fail!");
            }
            //在/appbricks下创建或删除节点触发appbricks
            if (weightFlag) {
                logger.info("create/update znode:" + weightLongPath + " succeed!");

                if (exists("/appbricks/temp")) {
                    delete("/appbricks/temp");
                    logger.info("delete znode:/appbricks/temp succeed!");
                } else {
                    createZnode("/appbricks/temp", "/appbricks", CreateMode.PERSISTENT);
                    logger.info("create znode:/appbricks/temp succeed!");
                }
            } else {
                logger.error("create/update znode:" + weightLongPath + " fail!");
            }
        } catch (KeeperException e) {
            logger.error("后台报错: " + e.getMessage(), e);
        } catch (InterruptedException e) {
            logger.error("后台报错: " + e.getMessage(), e);
        }

    }


    //创建结点
    public boolean createZnode(String path, String data, CreateMode type) throws KeeperException, InterruptedException {
        try {
            if (StringUtils.isNotEmpty(path)) {
                this.zkClient.create(path, data==null?null:data.getBytes(StandardCharsets.UTF_8), type);
                return true;
            }
        } catch (Exception e) {
            logger.error("创建节点时后台报错" + e.getMessage(), e);
        }
        return false;
    }

    //删除节点
    public boolean delete(String path) throws KeeperException, InterruptedException {
        try {
            if (StringUtils.isNotEmpty(path)) {
                this.zkClient.delete(path, -1);
                return true;
            }
        } catch (Exception e) {
            logger.error("删除节点时后台报错" + e.getMessage(), e);
        }
        return false;
    }

    //是否存在节点
    public boolean exists(String path) throws KeeperException, InterruptedException {
        return this.zkClient.exists(path);
    }

    //更新节点的值
    public boolean updateZnode(String path, String value) {
        try {
            if (StringUtils.isNotEmpty(path)) {
                this.zkClient.writeData(path, value==null?null:value.getBytes(StandardCharsets.UTF_8));
                return true;
            } else {
                return false;
            }

        } catch (Exception e) {
            logger.error("更新节点时后台报错" + e.getMessage(), e);
            return false;
        }
    }

    public void close() {
        zkClient.close();
    }


    /**
     * 遍历本地网卡，返回第一个合理的IP。
     *
     * @return 本地网卡IP
     */
    private InetAddress getLocalAddress0() {
        InetAddress localAddress = null;
        try {
            localAddress = InetAddress.getLocalHost();
            if (isValidAddress(localAddress)) {
                return localAddress;
            }
        } catch (Throwable e) {
            logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
            logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
        }
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            if (interfaces != null) {
                while (interfaces.hasMoreElements()) {
                    try {
                        NetworkInterface network = interfaces.nextElement();
                        Enumeration<InetAddress> addresses = network.getInetAddresses();
                        if (addresses != null) {
                            while (addresses.hasMoreElements()) {
                                try {
                                    InetAddress address = addresses.nextElement();
                                    if (isValidAddress(address)) {
                                        return address;
                                    }
                                } catch (Throwable e) {
                                    logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
                                }
                            }
                        }
                    } catch (Throwable e) {
                        logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
                    }
                }
            }
        } catch (Throwable e) {
            logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
        }
        logger.error("Could not get local host ip address of outer net, will use inner net ip or 127.0.0.1 instead.");
        return localAddress;
    }

    private boolean isValidAddress(InetAddress address) {
        //isLoopbackAddress:127.*,isSiteLocalAddress:局域网ip,isLinkLocalAddress:169.254.*,isMulticastAddress:224.0.0.0 ~ 239.255.255.255
        if (address != null) {
            String hostIp = address.getHostAddress();
            if (hostIp.startsWith("10.") || hostIp.startsWith("172.")) {
                return true;
            }
        }

        if (address == null || address.isLoopbackAddress() || address.isSiteLocalAddress() || address.isLinkLocalAddress()
                || address.isMulticastAddress() || isSpecialIp(address.getHostAddress())) {
            return false;
        }
        String name = address.getHostAddress();
        return (name != null && !ANYHOST.equals(name) && !LOCALHOST.equals(name) && IP_PATTERN.matcher(name).matches());
    }

    private boolean isSpecialIp(String ip) {
        if (ip.contains(":")) return true;
        if (ip.equals("255.255.255.255")) return true;
        return false;
    }

}
